﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Text;
using Dataglot;
using IrishSettings.Commands;
using IrishSettings.TableControllers;

namespace IrishSettings
{

    /// <summary>
    /// This class manages all complex transactions regarding the application table
    /// </summary>
    /// <remarks>
    /// An application is a name and a version associated with a collection of settings
    /// which each have a setting schema that can evolve with version upgrades.
    /// </remarks>
    public class DbApplicationManager:ConnectionConsumer, IDisposable
    {
        public DbApplicationManager(DataFactory factory):base(factory)
        {
        }

        public DbApplicationManager(DataFactory sharedFactory, DbConnection sharedConnection, TransactionManager sharedTransactionManager)
            :base(sharedFactory,sharedConnection,sharedTransactionManager)
        {   
        }


        public SettingsApplication CreateApplication(DataFactory sharedFactory, SettingsApplication appToCreate, DbSchemaManager schemaManager)
        {
            if(appToCreate.VersionObject==null)
                throw new InvalidOperationException("Version required");
            if (appToCreate.Application == null)
                throw new InvalidOperationException("Application name required");
            if (appToCreate.CultureObject == null)
                throw new InvalidOperationException("Culture required");

            SettingsApplication result;

            OpenConnection();
            BeginTransaction();
            ApplicationAndVersionController appController = new ApplicationAndVersionController(sharedFactory, con, Transaction);
            {
                if (!appController.ExistsByApplicationName(appToCreate.Application, appToCreate.VersionObject))
                {
                    //local schema object should just have an SettingsApplication property.
                    result = new SettingsApplication();
                    result.Application = appToCreate.Application;
                    result.Version = appToCreate.VersionObject.ToString();
                    result.Culture = appToCreate.CultureObject.ToString();
                    //TODO: allow updating other fields on creation
                    result.IsActiveVersion = true;
                    result = appController.Insert(result);
                }
                else
                {
                    result = appController.SelectByApplicationName(appToCreate.Application, appToCreate.VersionObject);
                }
            }

            CommitIfRequired();

            foreach (KeyValuePair<string, GenericSettingSchema> keyValuePair in appToCreate.Schema)
            {
                keyValuePair.Value.ApplicationId = result.Id;
            }

            //Ideally this would be available to the schema manager upon creation...
            schemaManager.CurrentApplication = appToCreate;
            if (!schemaManager.SchemaIsStored(result,appToCreate.VersionObject))
            {
                result.Schema = schemaManager.InsertSchema(appToCreate.Schema);
            }
            else
            {
                result.Schema = schemaManager.LoadSchemaForCurrentEnvironment(appToCreate.VersionObject);
            }
            
            
            if(result==null)
                throw new InvalidOperationException("application dto shouldn't be null at end of this method.");

            return result;
        }


        /// <summary>
        /// Remove an application, schema and values
        /// </summary>
        /// <param name="application"></param>
        /// <param name="version"></param>
        public void DeleteApplication(string application, Version version)
        {
            using (DeleteSchema command = new DeleteSchema(factory))
            {
                command.Delete(application, version);
            }
        }

        /// <summary>
        /// Create a new set of settings rows with a new version #.
        /// </summary>
        public void Upgrade(string application, Version newVersion)
        {
            //Copy Application
            OpenConnection();
            BeginTransaction();

            SettingsApplication current;
            using (ApplicationAndVersionController aavc = new ApplicationAndVersionController(factory,con, Transaction))
            {
                current = aavc.SelectByApplicationName(application, VersionCurrent(application));

                Version oldVersion = current.VersionObject;
                current.VersionObject = newVersion;
                
                SettingsApplication newApplication = aavc.Insert(current);

                //Copy Schema
                SchemaSettingsController schemaManager = new SchemaSettingsController(factory, con, Transaction);
                schemaManager.CopySchemaForNewVersion(newApplication, oldVersion, newVersion);

                //Copy Settings Values for Application
                SettingsForApplicationController sfac = new SettingsForApplicationController(factory,con, Transaction);
                sfac.CopyValuesForNewVersion(newApplication, oldVersion, newVersion);
            }

            CommitIfRequired();
        }

        public void Upgrade( Dictionary<Version, SettingsApplication> schemaSet, Version targetVersion)
        {
            //Copy
            //Apply changes from target version
            //Add those missing from DB
            //Remove those missing from target
            //Update defaults & maybe other attributes
            //Convert data types (limited!)

        }

        public Version VersionPrevious(string application)
        {
            using (SelectApplicationVersions command = new SelectApplicationVersions(factory))
            {
                List<Version> versions = command.Select(application);
                Version currentVersion = versions.Max();

                Version lastVersion = (from v in versions where v < currentVersion select v).Max();

                return lastVersion;
            }
        }

        public Version VersionCurrent(string application)
        {
            using (SelectApplicationVersions command = new SelectApplicationVersions(factory))
            {
                List<Version> versions = command.Select(application);
                Version currentVersion = versions.Max();
                return currentVersion;
            }
        }

        public SettingsApplication LoadApplication(string application, Version versionObject)
        {
            OpenConnection();
            
            using (ApplicationAndVersionController aavc = new ApplicationAndVersionController(factory,con, null))
            {
                return aavc.SelectByApplicationName(application, versionObject);
            }
        }

        public SettingsApplication LoadApplicationMaxVersion(string application)
        {
            OpenConnection();

            using (ApplicationAndVersionController aavc = new ApplicationAndVersionController(factory,con, null))
            {
                return aavc.SelectByApplicationName(application, VersionCurrent(application));
            }
        }
    }
}
